package com.legsem.jhosync;

import java.util.HashMap;
import java.util.Map;

import com.legsem.jhosync.change.Changes;
import com.legsem.jhosync.change.ChangesSet;
import com.legsem.jhosync.change.CreateLinks;
import com.legsem.jhosync.change.Deletes;
import com.legsem.jhosync.change.Inserts;

/**
 * Client is updated by receiving inserts and updates operations at the
 * key/attribute/value level.
 * <p/>
 * Updates are not sent all at once. Rather they are split in chunks, or pages,
 * of a limited size and sent progressively.
 * <p/>
 * This contains a page worth of notifications to be sent back to the client.
 * 
 */
public class ClientUpdatePages {

    private int progressCount;
    private int totalCount;
    private ChangesSet pageChangesSet;
    private String token;

    /**
     * Extract a page of changes from the remaining changes.
     * 
     * @param pSize page size
     * @param totalCount total number of primary keys in master and client
     *            documents
     * @param changes the remaining changes to be sent to client
     */
    public ClientUpdatePages(int pSize, int totalCount, ChangesSet changes) {

        pageChangesSet = new ChangesSet();

        int totalInsertsSize = 0;
        if (!changes.getInserts().empty()) {
            pageChangesSet.setInserts(new Inserts(trimToPage(pSize,
                    changes.getInserts())));
            totalInsertsSize = changes.getInserts().size();
        }
        if (!changes.getDeletes().empty()
                || !changes.getCreateDeletes().empty()) {
            pageChangesSet.setDeletes(new Deletes(trimToPage(
                    changes.getCreateDeletes(), pSize, changes.getDeletes())));
        }
        if (!changes.getCreateLinks().empty()) {
            pageChangesSet.setCreateLinks(new CreateLinks(trimToPage(pSize,
                    changes.getCreateLinks())));
        }
        pageChangesSet.setSourceErrors(changes.getSourceErrors());
        pageChangesSet.setCreateErrors(changes.getCreateErrors());
        pageChangesSet.setDeleteErrors(changes.getDeleteErrors());
        pageChangesSet.setUpdateErrors(changes.getUpdateErrors());

        pageChangesSet.setMetadata(changes.getMetadata());
        pageChangesSet.setMetadataSha1(changes.getMetadataSha1());

        this.progressCount = totalCount - totalInsertsSize;
        this.totalCount = totalCount;

    }

    /**
     * Constructor used when pages need to be resent as is.
     * 
     * @param progressCount how many primary keys are already processed
     * @param totalCounttotal number of primary keys
     * @param changesSet the changes to apply to client
     * @param token
     */
    public ClientUpdatePages(int progressCount, int totalCount,
            ChangesSet changeSet, String token) {

        this.totalCount = totalCount;
        this.progressCount = progressCount;
        pageChangesSet = changeSet;
        this.token = token;

    }

    /**
     * Return a map trimmed to a page size.
     * 
     * @param pSize the page size
     * @param map the complete map
     * @return a trimmed map
     */
    protected Map < String, Map < String, Object >> trimToPage(int pSize,
            Changes changes) {
        return trimToPage(null, pSize, changes.get());
    }

    /**
     * Return a map trimmed to a page size.
     * 
     * @param target where to store the trimmed changes
     * @param pSize the page size
     * @param changes the complete changes
     * @return a trimmed map
     */
    protected Map < String, Map < String, Object >> trimToPage(Changes target,
            int pSize, Changes changes) {
        return trimToPage(target.get(), pSize, changes.get());
    }

    /**
     * Adds elements from a map to a target in the limit of pSize elements.
     * 
     * @param pSize the page size
     * @param target the target map
     * @param map the complete map whose element need to be added to the target
     * @return a trimmed accumulated map (target + map)
     */
    protected static Map < String, Map < String, Object >> trimToPage(
            Map < String, Map < String, Object >> target, int pSize,
            Map < String, Map < String, Object >> map) {
        Map < String, Map < String, Object >> trimmedMap = null;
        int size = 0;

        if (target == null) {
            if (map == null || map.size() <= pSize) {
                return map;
            }
            trimmedMap = new HashMap < String, Map < String, Object >>();
        } else {
            trimmedMap = new HashMap < String, Map < String, Object >>(target);
            size = target.size();
        }
        if (map != null) {
            for (String key : map.keySet()) {
                if (size >= pSize) {
                    break;
                }
                trimmedMap.put(key, map.get(key));
                size++;
            }
        }
        return trimmedMap;
    }

    public int getProgressCount() {
        return progressCount;
    }

    /**
     * @return the number of keys in the master document. The objective is to
     *         ultimately have that same count on the client.
     */
    public int getTotalCount() {
        return totalCount;
    }

    /**
     * @return the number of update operations in this chunk
     */
    public int getCount() {
        return getPageChanges().getInserts().size()
                + getPageChanges().getDeletes().size();
    }

    /**
     * @return true if there are changes in this message that the client needs
     *         to acknowledge
     */
    public boolean ackNeeded() {
        return !getPageChanges().getInserts().empty()
                || !getPageChanges().getDeletes().empty()
                || !getPageChanges().getCreateLinks().empty();
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public ChangesSet getPageChanges() {
        return pageChangesSet;
    }

    @Override
    public String toString() {
        return "ClientUpdatePages [progressCount=" + progressCount
                + ", totalCount=" + totalCount + ", pageChangesSet="
                + pageChangesSet + ", token=" + token + "]";
    }

}
